#region Header
// ------------------------ Licence / Copyright ------------------------
// 
// Copyright (c) 2008 Daniel Grunwald
// 
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
// 
// Author:
//  Daniel Grunwald
//
// ---------------------------------------------------------------------
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;

namespace SSATool.Common.Util.WeakEvents
{
    /// <summary>
    /// The forwarder-generating code is in a separate class because it does not depend on type T.
    /// </summary>
    /// <remarks>
    /// See http://www.codeproject.com/KB/cs/WeakEvents.aspx
    /// </remarks>
	static class FastSmartWeakEventForwarderProvider
	{
		static readonly MethodInfo getTarget = typeof(WeakReference).GetMethod("get_Target");
		static readonly Type[] forwarderParameters = { typeof(WeakReference), typeof(object), typeof(EventArgs) };
		internal delegate bool ForwarderDelegate(WeakReference wr, object sender, EventArgs e);
		
		static readonly Dictionary<MethodInfo, ForwarderDelegate> forwarders = new Dictionary<MethodInfo, ForwarderDelegate>();
		
		internal static ForwarderDelegate GetForwarder(MethodInfo method)
		{
			lock (forwarders) {
				ForwarderDelegate d;
				if (forwarders.TryGetValue(method, out d))
					return d;
			}
			
			if (method.DeclaringType.GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Length != 0)
				throw new ArgumentException("Cannot create weak event to anonymous method with closure.");
			var parameters = method.GetParameters();
			
			Debug.Assert(getTarget != null);
			
			DynamicMethod dm = new DynamicMethod(
				"FastSmartWeakEvent", typeof(bool), forwarderParameters, method.DeclaringType);
			
			ILGenerator il = dm.GetILGenerator();
			
			if (!method.IsStatic) {
				il.Emit(OpCodes.Ldarg_0);
				il.EmitCall(OpCodes.Callvirt, getTarget, null);
				il.Emit(OpCodes.Dup);
				Label label = il.DefineLabel();
				il.Emit(OpCodes.Brtrue, label);
				il.Emit(OpCodes.Pop);
				il.Emit(OpCodes.Ldc_I4_1);
				il.Emit(OpCodes.Ret);
				il.MarkLabel(label);
				// The castclass here is required for the generated code to be verifiable.
				// We can leave it out because we know this cast will always succeed
				// (the instance/method pair was taken from a delegate).
				// Unverifiable code is fine because private reflection is only allowed under FullTrust
				// anyways.
				//il.Emit(OpCodes.Castclass, method.DeclaringType);
			}
			il.Emit(OpCodes.Ldarg_1);
			il.Emit(OpCodes.Ldarg_2);
			// This castclass here is required to prevent creating a hole in the .NET type system.
			// See Program.TypeSafetyProblem in the 'SmartWeakEventBenchmark' to see the effect when
			// this cast is not used.
			// You can remove this cast if you trust add FastSmartWeakEvent.Raise callers to do
			// the right thing, but the small performance increase (about 5%) usually isn't worth the risk.
			il.Emit(OpCodes.Castclass, parameters[1].ParameterType);
			
			il.EmitCall(OpCodes.Call, method, null);
			il.Emit(OpCodes.Ldc_I4_0);
			il.Emit(OpCodes.Ret);
			
			ForwarderDelegate fd = (ForwarderDelegate)dm.CreateDelegate(typeof(ForwarderDelegate));
			lock (forwarders) {
				forwarders[method] = fd;
			}
			return fd;
		}
	}
}
